/*
 * @(#) $Header$
 *
 * Copyright (C) 2011  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;

import java.io.IOException;

import static ca.forklabs.baselib.io.Streams.asBufferedReader;
import static ca.forklabs.baselib.io.Streams.asObjectInputStream;
import static ca.forklabs.baselib.io.Streams.asObjectOutputStream;

/**
 * Class {@code StreamUtil} provides utility classes to make it easier to read,
 * write and append to streams and readers by offering factory methods for
 * streams and reader/writers as well as one-liners to read and write.
 * <p>
 * <b>NOTE</b>: appending using charset {@link #UTF_16} will put a
 * <a href="http://en.wikipedia.org/wiki/UTF-16/UCS-2#Byte_order_encoding_schemes">Zero-width non-breaking space</a>
 * for each append call.
 *
 * @author   <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.io.StreamUtil">Daniel Léonard</a>
 * @version $Revision$
 */
public class StreamUtil extends FileUtil {

//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    */
   protected StreamUtil() {
   // allow sub-classes only
      }


//---------------------------
// Factory methods
//---------------------------
// TODO: move to class Streams
//   /**
//    * Creates an unlimited input stream that always returns <em>0</em> when
//    * read.
//    * @return   a constant input stream.
//    */
//   public static InputStream zero() {
//      InputStream is = new ZeroInputStream();
//      return is;
//      }
//
//   /**
//    * Creates an unlimited input stream that always returns the same constant
//    * value when read.
//    * @param   value   the constant value.
//    * @return   a constant input stream.
//    */
//   public static InputStream constant(byte value) {
//      InputStream is = new ConstantInputStream(value);
//      return is;
//      }
//
//   /**
//    * Creates an unlimited input stream that always return random data when
//    * read.
//    * @return   a random input stream.
//    */
//   public static InputStream random() {
//      Random random = new Random();
//      InputStream is = random(random);
//      return is;
//      }
//
//   /**
//    * Creates an unlimited input stream that always return random data when
//    * read.
//    * @param   random   the source of random values.
//    * @return   a random input stream.
//    */
//   public static InputStream random(Random random) {
//      InputStream is = new RandomInputStream(random);
//      return is;
//      }
//
//   /**
//    * Creates an output stream that will write the same thing in all the given
//    * streams.
//    * @param   streams   the streams.
//    * @return   an output stream.
//    */
//   public static OutputStream fork(OutputStream... streams) {
//      OutputStream os = fork(Arrays.asList(streams));
//      return os;
//      }
//
//   /**
//    * Creates an output stream that will write the same thing in all the given
//    * streams.
//    * @param   streams   the streams.
//    * @return   an output stream.
//    */
//   public static OutputStream fork(Iterable<OutputStream> streams) {
//      OutputStream os = new ForkOutputStream(streams);
//      return os;
//      }
//
//   /**
//    * Creates a writer that will write the same thing in all the given writers.
//    * @param   writers   the writers.
//    * @return   a writer.
//    */
//   public static Writer fork(Writer... writers) {
//      Writer writer = fork(Arrays.asList(writers));
//      return writer;
//      }
//
//   /**
//    * Creates a writer that will write the same thing in all the given writers.
//    * @param   writers   the writers.
//    * @return   a writer.
//    */
//   public static Writer fork(Iterable<Writer> writers) {
//      Writer writer = new ForkWriter(writers);
//      return writer;
//      }

//---------------------------
// readAllBytes()
//---------------------------

   /**
    * Opens, reads up to {@link Integer#MAX_VALUE} bytes from and closes the
    * specified file.
    * @param   filename   the file to read.
    * @return   the content.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static byte[] readAllBytes(String filename) throws IOException {
      InputStream is = openRead(filename);
      byte[] bytes = readAllBytesAndClose(is);
      return bytes;
      }

   /**
    * Opens, reads up to {@link Integer#MAX_VALUE} bytes from and closes the
    * specified file.
    * @param   file   the file to read.
    * @return   the content.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static byte[] readAllBytes(File file) throws IOException {
      InputStream is = openRead(file);
      byte[] bytes = readAllBytesAndClose(is);
      return bytes;
      }

   /**
    * Opens a connection and reads up to {@link Integer#MAX_VALUE} bytes from
    * the specified URL.
    * @param   url   the url to open.
    * @return   the content.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static byte[] readAllBytes(URL url) throws IOException {
      InputStream is = openRead(url);
      byte[] bytes = readAllBytesAndClose(is);
      return bytes;
      }

   /**
    * Reads all the bytes from the java resource.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @return   all the bytes.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static byte[] readAllBytes(Class<?> clazz, String resource) throws IOException {
      InputStream is = openRead(clazz, resource);
      byte[] bytes = readAllBytesAndClose(is);
      return bytes;
      }

   /**
    * Reads all the bytes from the given stream then closes it.
    * @param   is   the stream.
    * @return   all the bytes.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static byte[] readAllBytesAndClose(InputStream is) throws IOException {
      byte[] bytes;
      try {
         bytes = readAllBytes(is);
         }
      finally {
         is.close();
         }
      return bytes;
      }

   /**
    * Reads all the bytes in the stream. The space complexity of this algorithm
    * is twice as big as the number of bytes read. First all the bytes are read
    * into chunks, then all the chunks are spliced together and the final
    * product is returned.
    * @param   is   the stream.
    * @return   all the bytes.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static byte[] readAllBytes(InputStream is) throws IOException {
      List<byte[]> list = new LinkedList<>();

   // read and gather the chunks
      int read;
      do {
         int max = 4096;
         byte[] b = new byte[max];

         read = is.read(b);

         if (-1 != read) {
            byte[] bytes = b;
            if (read != max) {
               bytes = new byte[read];
               System.arraycopy(b, 0, bytes, 0, read);
               }
            list.add(bytes);
            }
         }
      while (-1 != read);

   // splice into a single array
      int len = 0;
      for (byte[] b : list) {
         len += b.length;
         }

      byte[] bytes = new byte[len];
      int offset = 0;
      for (byte[] b : list) {
         len = b.length;
         System.arraycopy(b, 0, bytes, offset, len);
         offset += len;
         }

   // clean up
      list.clear();

      return bytes;
      }


//---------------------------
// readAllLines()
//---------------------------

   /**
    * Opens, reads all the lines and closes the specified file.
    * @param   filename   the file to read.
    * @return   all the lines.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLines(String filename) throws IOException {
      Charset charset = Charset.defaultCharset();
      String[] lines = readAllLines(filename, charset);
      return lines;
      }

   /**
    * Opens, reads all the lines and closes the specified file.
    * @param   file   the file to read.
    * @return   all the lines.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLines(File file) throws IOException {
      Charset charset = Charset.defaultCharset();
      String[] lines = readAllLines(file, charset);
      return lines;
      }

   /**
    * Reads all the lines of the specified input steam. This is a convenience
    * for text streams whose origin are not files, such as but not limited to
    * URL and resources.
    * @param   is   the stream.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLines(InputStream is) throws IOException {
      Charset charset = Charset.defaultCharset();
      String[] lines = readAllLines(is, charset);
      return lines;
      }

   /**
    * Reads all the lines of the specified URL.
    * @param   url   the url.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLines(URL url) throws IOException {
      Charset charset = Charset.defaultCharset();
      String[] lines = readAllLines(url, charset);
      return lines;
      }

   /**
    * Reads all the lines from the java resource.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLines(Class<?> clazz, String resource) throws IOException {
      Charset charset = Charset.defaultCharset();
      String[] lines = readAllLines(clazz, resource, charset);
      return lines;
      }

   /**
    * Opens, reads all the lines and closes the specified file.
    * @param   filename   the file to read.
    * @param   charset   the encoding of the text.
    * @return   all the lines.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(String filename, Charset charset) throws IOException {
      Reader reader = openReadText(filename, charset);
      String[] lines = readAllLinesAndClose(reader);
      return lines;
      }

   /**
    * Opens, reads all the lines and closes the specified file.
    * @param   file   the file to read.
    * @param   charset   the encoding of the text.
    * @return   all the lines.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(File file, Charset charset) throws IOException {
      Reader reader = openReadText(file, charset);
      String[] lines = readAllLinesAndClose(reader);
      return lines;
      }

   /**
    * Reads all the lines of the specified input steam. This is a convenience
    * for text streams whose origin are not files, such as but not limited to
    * URL and resources.
    * @param   is   the stream.
    * @param   charset   the encoding of the text.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(InputStream is, Charset charset) throws IOException {
      Reader reader = openReadText(is, charset);
      String[] lines = readAllLinesAndClose(reader);
      return lines;
      }

   /**
    * Reads all the lines of the specified URL.
    * @param   url   the url.
    * @param   charset   the encoding of the text.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(URL url, Charset charset) throws IOException {
      Reader reader = openReadText(url, charset);
      String[] lines = readAllLinesAndClose(reader);
      return lines;
      }

   /**
    * Reads all the lines from the java resource.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @param   charset   the encoding of the text.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(Class<?> clazz, String resource, Charset charset) throws IOException {
      Reader reader = openReadText(clazz, resource, charset);
      String[] lines = readAllLinesAndClose(reader);
      return lines;
      }

   /**
    * Reads all the lines and closes the reader.
    * @param   reader   the reader.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String[] readAllLinesAndClose(Reader reader) throws IOException {
      String[] lines;
      try {
         lines = readAllLines(reader);
         }
      finally {
         reader.close();
         }
      return lines;
      }

   /**
    * Reads all the lines of the specified reader.
    * @param   reader   the reader.
    * @return   all the lines.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String[] readAllLines(Reader reader) throws IOException {
      List<String> list = new LinkedList<>();

      BufferedReader br = asBufferedReader(reader);
      for (String line = br.readLine(); null != line; line = br.readLine()) {
         list.add(line);
         }

      String[] lines = list.toArray(new String[0]);
      return lines;
      }


//---------------------------
// readAllText()
//---------------------------

   /**
    * Opens, reads all the text and closes the specified file.
    * @param   filename   the file to read.
    * @return   all the text.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(String filename) throws IOException {
      Charset charset = Charset.defaultCharset();
      String text = readAllText(filename, charset);
      return text;
      }

   /**
    * Opens, reads all the text and closes the specified file.
    * @param   file   the file to read.
    * @return   all the text.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(File file) throws IOException {
      Charset charset = Charset.defaultCharset();
      String text = readAllText(file, charset);
      return text;
      }

   /**
    * Reads all the text of the specified input steam. This is a convenience
    * for text streams whose origin are not files, such as but not limited to
    * URL and resources.
    * @param   is   the stream.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(InputStream is) throws IOException {
      Charset charset = Charset.defaultCharset();
      String text = readAllText(is, charset);
      return text;
      }

   /**
    * Reads all the text of the specified URL.
    * @param   url   the url.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(URL url) throws IOException {
      Charset charset = Charset.defaultCharset();
      String text = readAllText(url, charset);
      return text;
      }

   /**
    * Reads all the text from the java resource.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(Class<?> clazz, String resource) throws IOException {
      Charset charset = Charset.defaultCharset();
      String text = readAllText(clazz, resource, charset);
      return text;
      }

   /**
    * Opens, reads all the text and closes the specified file.
    * @param   filename   the file to read.
    * @param   charset   the encoding of the text.
    * @return   all the text.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String readAllText(String filename, Charset charset) throws IOException {
      Reader reader = openReadText(filename, charset);
      String text = readAllTextAndClose(reader);
      return text;
      }

   /**
    * Opens, reads all the text and closes the specified file.
    * @param   file   the file to read.
    * @param   charset   the encoding of the text.
    * @return   all the text.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String readAllText(File file, Charset charset) throws IOException {
      Reader reader = openReadText(file, charset);
      String text = readAllTextAndClose(reader);
      return text;
      }

   /**
    * Reads all the text of the specified reader. This is a convenience for text
    * streams whose origin are not files, such as but not limited to URL and
    * resources.
    * @param   is   the stream.
    * @param   charset   the encoding of the text.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(InputStream is, Charset charset) throws IOException {
      InputStreamReader reader = new InputStreamReader(is, charset);
      String text = readAllTextAndClose(reader);
      return text;
      }

   /**
    * Reads all the text of the specified URL.
    * @param   url   the url.
    * @param   charset   the encoding of the text.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String readAllText(URL url, Charset charset) throws IOException {
      Reader reader = openReadText(url, charset);
      String text = readAllTextAndClose(reader);
      return text;
      }

   /**
    * Reads all the text from the java resource.
    * @param   clazz   a class.
    * @param   resource   the name of the resource.
    * @param   charset   the encoding of the text.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static String readAllText(Class<?> clazz, String resource, Charset charset) throws IOException {
      Reader reader = openReadText(clazz, resource, charset);
      String text = readAllTextAndClose(reader);
      return text;
      }

   /**
    * Reads all the text and closes the reader.
    * @param   reader   the reader.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllTextAndClose(Reader reader) throws IOException {
      String text;
      try {
         text = readAllText(reader);
         }
      finally {
         reader.close();
         }
      return text;
      }

   /**
    * Reads all the text of the specified reader.
    * @param   reader   the reader.
    * @return   all the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static String readAllText(Reader reader) throws IOException {
      StringBuilder sb = new StringBuilder();

      char[] chars = new char[4096];
      for (int read = reader.read(chars); -1 != read; read = reader.read(chars)) {
         sb.append(chars, 0, read);
         }

      String text = sb.toString();
      return text;
      }


//---------------------------
// readObject()
//---------------------------

   /**
    * Reads the first object from the given file.
    * @param   filename   the name of the file.
    * @return   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    * @throws   ClassNotFoundException   if the class of the object is not
    *                                    found.
    * @see   #readObject(InputStream)
    */
   @SuppressWarnings("resource")
   public static Object readObject(String filename) throws IOException, ClassNotFoundException {
      InputStream is = openRead(filename);
      Object o = readObjectAndClose(is);
      return o;
      }

   /**
    * Reads the first object from the given file.
    * @param   file   the file.
    * @return   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    * @throws   ClassNotFoundException   if the class of the object is not
    *                                    found.
    * @see   #readObject(InputStream)
    */
   @SuppressWarnings("resource")
   public static Object readObject(File file) throws IOException, ClassNotFoundException {
      InputStream is = openRead(file);
      Object o = readObjectAndClose(is);
      return o;
      }

   /**
    * Reads the first object from the given url.
    * @param   url   the url.
    * @return   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    * @throws   ClassNotFoundException   if the class of the object is not
    *                                    found.
    * @see   #readObject(InputStream)
    */
   @SuppressWarnings("resource")
   public static Object readObject(URL url) throws IOException, ClassNotFoundException {
      InputStream is = openRead(url);
      Object o = readObjectAndClose(is);
      return o;
      }

   /**
    * Reads the first object and closes the stream.
    * @param   is   the stream
    * @return   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    * @throws   ClassNotFoundException   if the class of the object is not
    *                                    found.
    */
   public static Object readObjectAndClose(InputStream is) throws IOException, ClassNotFoundException {
      Object o;

      try (ObjectInputStream ois = asObjectInputStream(is)) {
         o = readObject(ois);
         }

      return o;
      }

   /**
    * Reads the first object from the given stream.
    * @param   is   the stream.
    * @return   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    * @throws   ClassNotFoundException   if the class of the object is not
    *                                    found.
    * @see   ObjectInputStream#readObject()
    */
   @SuppressWarnings("resource")
   public static Object readObject(InputStream is) throws IOException, ClassNotFoundException {
      ObjectInputStream ois = asObjectInputStream(is);
      Object o = ois.readObject();
      return o;
      }


//---------------------------
// writeAllBytes()
//---------------------------

   /**
    * Opens a new file, writes all the bytes and closes it.
    * @param   filename   the new file to write to.
    * @param   bytes   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllBytes(String filename, byte[] bytes) throws IOException {
      OutputStream os = openWrite(filename);
      writeAllBytesAndClose(os, bytes);
      }

   /**
    * Opens a new file, writes all the bytes and closes it.
    * @param   file   the new file to write to.
    * @param   bytes   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllBytes(File file, byte[] bytes) throws IOException {
      OutputStream os = openWrite(file);
      writeAllBytesAndClose(os, bytes);
      }

   /**
    * Writes all the bytes to the given stream and closes it.
    * @param   os   the stream.
    * @param   bytes   the bytes.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllBytesAndClose(OutputStream os, byte[] bytes) throws IOException {
      try {
         writeAllBytes(os, bytes);
         }
      finally {
         os.close();
         }
      }

   /**
    * Writes and flushes all the bytes to the given output stream. This is a
    * convenience for streams whose destination are not files, such as but not
    * limited to sockets.
    * @param   os   the output stream.
    * @param   bytes   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllBytes(OutputStream os, byte[] bytes) throws IOException {
      os.write(bytes);
      os.flush();
      }


//---------------------------
// writedAllLines()
//---------------------------

   /**
    * Opens a new file, writes all the lines and closes it. The lines are
    * separated by the default line separator.
    * @param   filename   the new file to write to.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllLines(String filename, CharSequence... lines) throws IOException {
      Charset charset = Charset.defaultCharset();
      writeAllLines(filename, charset, lines);
      }

   /**
    * Opens a new file, writes all the lines and closes it. The lines are
    * separated by the default line separator.
    * @param   file   the new file to write to.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllLines(File file, CharSequence... lines) throws IOException {
      Charset charset = Charset.defaultCharset();
      writeAllLines(file, charset, lines);
      }

   /**
    * Opens a new file, writes all the lines and closes it. The lines are
    * separated by the default line separator.
    * @param   filename   the new file to write to.
    * @param   charset   the encoding of the text.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllLines(String filename, Charset charset, CharSequence... lines) throws IOException {
      Writer writer = openWriteText(filename, charset);
      writeAllLinesAndClose(writer, lines);
      }

   /**
    * Opens a new file, writes all the lines and closes it. The lines are
    * separated by the default line separator.
    * @param   file   the new file to write to.
    * @param   charset   the encoding of the text.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllLines(File file, Charset charset, CharSequence... lines) throws IOException {
      Writer writer = openWriteText(file, charset);
      writeAllLinesAndClose(writer, lines);
      }

   /**
    * Writes all the lines and closes the writer.
    * @param   writer   the writer.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllLinesAndClose(Writer writer, CharSequence... lines) throws IOException {
      try {
         writeAllLines(writer, lines);
         }
      finally {
         writer.close();
         }
      }

   /**
    * Writes and flushes all the lines to the given writer. This is a
    * convenience for text streams whose destination are not files, such as but
    * not limited to sockets.
    * @param   writer   the writer.
    * @param   lines   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllLines(Writer writer, CharSequence... lines) throws IOException {
      String eol = eol();
      for (CharSequence line : lines) {
         String text = line.toString();
         writer.write(text);
         writer.write(eol);
         }
      writer.flush();
      }


//---------------------------
// writeAllText()
//---------------------------

   /**
    * Opens a new file, writes all the text and closes it.
    * @param   filename   the new file to write to.
    * @param   text   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllText(String filename, CharSequence text) throws IOException {
      Charset charset = Charset.defaultCharset();
      writeAllText(filename, charset, text);
      }

   /**
    * Opens a new file, writes all the text and closes it.
    * @param   file   the new file to write to.
    * @param   text   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllText(File file, CharSequence text) throws IOException {
      Charset charset = Charset.defaultCharset();
      writeAllText(file, charset, text);
      }

   /**
    * Opens a new file, writes all the text and closes it.
    * @param   filename   the new file to write to.
    * @param   charset   the encoding of the text.
    * @param   text   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllText(String filename, Charset charset, CharSequence text) throws IOException {
      Writer writer = openWriteText(filename, charset);
      writeAllTextAndClose(writer, text);
      }

   /**
    * Opens a new file, writes all the text and closes it.
    * @param   file   the new file to write to.
    * @param   charset   the encoding of the text.
    * @param   text   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeAllText(File file, Charset charset, CharSequence text) throws IOException {
      Writer writer = openWriteText(file, charset);
      writeAllTextAndClose(writer, text);
      }

   /**
    * Writes all the text and closes the writer.
    * @param   writer   the writer.
    * @param   text   the text.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllTextAndClose(Writer writer, CharSequence text) throws IOException {
      try {
         writeAllText(writer, text);
         }
      finally {
         writer.close();
         }
      }

   /**
    * Writes and flushes all the text to the given writer. This is a convenience
    * for text streams whose destination are not files, such as but not limited
    * to sockets.
    * @param   writer   the writer.
    * @param   text   the content to write.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeAllText(Writer writer, CharSequence text) throws IOException {
      writer.write(text.toString());
      writer.flush();
      }


//---------------------------
// appendAllBytes()
//---------------------------

   /**
    * Opens a file, appends all the data and closes it.
    * @param   filename   the file to write to.
    * @param   bytes   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllBytes(String filename, byte[] bytes) throws IOException {
      OutputStream os = openAppend(filename);
      writeAllBytesAndClose(os, bytes);
      }

   /**
    * Opens a file, appends all the data and closes it.
    * @param   file   the file to write to.
    * @param   bytes   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllBytes(File file, byte[] bytes) throws IOException {
      OutputStream os = openAppend(file);
      writeAllBytesAndClose(os, bytes);
      }


//---------------------------
// appendAllLines()
//---------------------------

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   filename   the file to write to.
    * @param   lines   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static void appendAllLines(String filename, CharSequence... lines) throws IOException {
      Charset charset = Charset.defaultCharset();
      appendAllLines(filename, charset, lines);
      }

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   file   the file to write to.
    * @param   lines   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static void appendAllLines(File file, CharSequence... lines) throws IOException {
      Charset charset = Charset.defaultCharset();
      appendAllLines(file, charset, lines);
      }

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   filename   the file to write to.
    * @param   charset   the encoding of the text.
    * @param   lines   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllLines(String filename, Charset charset, CharSequence... lines) throws IOException {
      Writer writer = openAppendText(filename, charset);
      writeAllLinesAndClose(writer, lines);
      }

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   file   the file to write to.
    * @param   charset   the encoding of the text.
    * @param   lines   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllLines(File file, Charset charset, CharSequence... lines) throws IOException {
      Writer writer = openAppendText(file, charset);
      writeAllLinesAndClose(writer, lines);
      }


//---------------------------
// appendAllText()
//---------------------------

   /**
    * Opens a file, appends all the text and closes it.
    * @param   filename   the file to write to.
    * @param   text   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static void appendAllText(String filename, CharSequence text) throws IOException {
      Charset charset = Charset.defaultCharset();
      appendAllText(filename, charset, text);
      }

   /**
    * Opens a file, appends all the text and closes it.
    * @param   file   the file to write to.
    * @param   text   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   public static void appendAllText(File file, CharSequence text) throws IOException {
      Charset charset = Charset.defaultCharset();
      appendAllText(file, charset, text);
      }

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   filename   the file to write to.
    * @param   charset   the encoding of the text.
    * @param   text   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllText(String filename, Charset charset, CharSequence text) throws IOException {
      Writer writer = openAppendText(filename, charset);
      writeAllTextAndClose(writer, text);
      }

   /**
    * Opens a file, appends all the lines and closes it.
    * @param   file   the file to write to.
    * @param   charset   the encoding of the text.
    * @param   text   the content to write.
    * @throws IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void appendAllText(File file, Charset charset, CharSequence text) throws IOException {
      Writer writer = openAppendText(file, charset);
      writeAllTextAndClose(writer, text);
      }


//---------------------------
// writeObject()
//---------------------------

   /**
    * Writes the object to the given file.
    * @param   filename   the name of the file
    * @param   o   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeObject(String filename, Object o) throws IOException {
      OutputStream os = openWrite(filename);
      writeObjectAndClose(os, o);
      }

   /**
    * Writes the object to the given file.
    * @param   file   the file
    * @param   o   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeObject(File file, Object o) throws IOException {
      OutputStream os = openWrite(file);
      writeObjectAndClose(os, o);
      }

   /**
    * Writes the object and closes the stream.
    * @param   os   the output stream.
    * @param   o   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void writeObjectAndClose(OutputStream os, Object o) throws IOException {
      try (ObjectOutputStream oos = asObjectOutputStream(os);) {
         writeObject(oos, o);
         }
      }

   /**
    * Writes the object to the given stream.
    * @param   os   the stream.
    * @param   o   the object.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   @SuppressWarnings("resource")
   public static void writeObject(OutputStream os, Object o) throws IOException {
      ObjectOutputStream oos = asObjectOutputStream(os);
      oos.writeObject(o);
      oos.flush();
      }


//---------------------------
// copy()
//---------------------------

   /**
    * Copies one stream into the other.
    * @param   is   the source stream.
    * @param   os   the sink stream.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void copy(InputStream is, OutputStream os) throws IOException {
   // if both are file streams, shortcut using channels
      if (is instanceof FileInputStream && os instanceof FileOutputStream) {
         FileInputStream fis = (FileInputStream) is;
         FileOutputStream fos = (FileOutputStream) os;
         copy(fis, fos);
         }
      else {
         byte[] bytes = new byte[8 * 1024];
         for (int count = is.read(bytes); -1 != count; count = is.read(bytes)) {
            os.write(bytes, 0, count);
            }
         }
      }

   /**
    * Copies one reader into a writer.
    * @param   reader   the reader.
    * @param   writer   the writer.
    * @throws   IOException   if anything goes wrong with I/O.
    */
   public static void copy(Reader reader, Writer writer) throws IOException {
      char[] chars = new char[8 * 1024];
      for (int count = reader.read(chars); -1 != count; count = reader.read(chars)) {
         writer.write(chars, 0, count);
         }
      }

   }
